﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD.Core {

    /// <summary>
    /// フィードバック バッファー クラス
    /// </summary>
    /// <remarks>
    /// フィードバック バッファーは複数の頂点アレイ <see cref="VertexArray"/> を保持するコンテナクラスで、
    /// 頂点シェーダーの出力を受け取ります。
    /// <para>
    /// <h3>頂点フィードバック</h3>
    /// 頂点シェーダーからの出力を（OpenGL用語で）<c>Varying</c> 変数と言います。これは通常フラグメント シェーダーの入力となります。
    /// 頂点フィードバック機能を使用するとこの <c>Varygin</c> 変数を”フック”して取り出す事が可能です。
    /// フックしたデータは頂点アレイ <see cref="VertexArray"/> オブジェクトに収納され、次のレンダリング パスで
    /// 頂点シェーダーの入力として使用する事が可能です。
    /// この機能は典型的にはキャラクターをアニメーションするのに使用されます。
    /// スケレタル アニメーションの詳細については、<see cref="SkinnedMesh"/> の解説を参照してください。
    /// <note type="implement">
    /// 現在NVIDIAのビデオカードでこの頂点フィードバック機能が正しく動作しない現象を確認しています。
    /// 具体的にはフックする <c>Varying</c> 変数に指定した名前が文字化けしてセットできません。
    /// NVIDIAの問題なので DDD ではどうにもなりません。
    /// 回避方法は不明です。
    /// </note>
    /// </para>
    /// </remarks>
    /// <seealso cref="VertexBuffer"/>
    /// <seealso cref="SkinnedMesh"/>
    public class FeedbackBuffer : Object {

        #region Field
        List<FeedbackVariable> feedbacks;   // ぱっと見でVertexArrayを持っていることがわかりにくい。変更すべきか？
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 初期値は以下のとおりです。
        /// <list type="bullet">
        ///   <item><see cref="FeedbackVaryings"/> = なし</item>
        /// </list>
        /// 
        /// </remarks>
        public FeedbackBuffer () {
            this.feedbacks = new List<FeedbackVariable> ();
        }
        #endregion

        #region Property
        /// <summary>
        /// フィードバック <c>Varying</c> 変数の総数
        /// </summary>
        /// <remarks>
        /// 現在フィードバック用に設定されている <c>Varying</c> 変数の総数です。
        /// </remarks>
        public int FeedbackVaryingCount {
            get { return feedbacks.Count; }
        }


        /// <summary>
        /// フィードバック <c>Varying</c> 変数を列挙する列挙子
        /// </summary>
        public IEnumerable<FeedbackVariable> FeedbackVaryings {
            get { return feedbacks; }
        }

        /// <summary>
        /// <see cref="VertexArray"/> を列挙する列挙子
        /// </summary>
        /// <remarks>
        /// フィードバック <c>Varying</c> 変数を受け取る頂点アレイの総数です。
        /// <c>null</c>が含まれる事はありません。
        /// </remarks>
        public IEnumerable<VertexArray> VertexArrays {
            get {
                return from x in feedbacks
                       where x.VertexArray != null
                       select x.VertexArray;
            }
        }

        /// <summary>
        /// 頂点数
        /// </summary>
        /// <remarks>
        /// このフィードバックバッファーで使用する頂点アレイ <see cref="VertexArray"/> の頂点数です。
        /// すべての頂点アレイで同一である必要があります。
        /// まだ頂点アレイが登録されていない場合0が返ります。
        /// </remarks>
        public int VertexCount {
            get {
                if (feedbacks.Count > 0) {
                    return feedbacks[0].VertexArray.VertexCount;
                }
                return 0;
            }
        }

        /// <inheritdoc/>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.AddRange (this.feedbacks.Select (x => x.VertexArray));
                objs.RemoveAll (x => x == null);
                return objs;
            }
        }

        #endregion

        #region Method

        /// <summary>
        /// フィードバック <c>Varying</c> 変数の登録
        /// </summary>
        /// <remarks>
        /// フックするフィードバック <c>Varying</c> 変数名 <paramref name="name"/> と、
        /// データ受け取り用の頂点アレイ <see cref="VertexArray"/> <paramref name="varry"/> を登録します。
        /// <c>Varying</c> 変数と <see cref="VertexArray"/> オブジェクトは1：1で対応します。
        /// 既に登録済みの名前の場合はこれを新しい物に置き換えます。
        /// </remarks>
        /// <param name="name">フィードバックする <c>Varying</c> 変数の名前</param>
        /// <param name="varry">データを収納する頂点アレイ</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> が <c>null</c> または空文字列</exception>
        /// <exception cref="ArgumentNullException"><paramref name="varry"/> が <c>null</c></exception>
        /// <exception cref="ArgumentException">登録済みの <see cref="VertexArray"/> があって、なおかつ <paramref name="varry"/>.<see cref="VertexArray.VertexCount"/> が既存のものと異なる</exception>
        public void AddVertexArray (string name, VertexArray varry) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            if (varry == null) {
                throw new ArgumentNullException ("VertexArray is null");
            }
            if (VertexCount > 0 && VertexCount != varry.VertexCount) {
                throw new ArgumentException ("All VertexCount must be equal in the same FeedbackBuffer");
            }
            // 同一のVertexArrayを異なる名前で登録するのもエラーにすべきだよね？

            this.feedbacks.RemoveAll (x => x.Name == name);
            this.feedbacks.Add (new FeedbackVariable (name, varry, null));
        }

        /// <summary>
        /// フィードバック <c>Varying</c> 変数の登録
        /// </summary>
        /// <remarks>
        /// このメソッドは廃止された。呼び出すと必ず例外が発生する。
        /// <note type="implement">
        /// <paramref name="copyTo"/> 付きの <see cref="AddVertexArray(string,VertexArray,VertexArray)"/> は廃止された。
        /// 代わりに <paramref name="copyTo"/> の付かない <see cref="AddVertexArray(string,VertexArray)"/> を使用してください。
        /// TODO: このメソッドの廃止に伴う影響をチェックせよ。完全削除はそれまで延期
        /// </note>
        /// 
        /// </remarks>
        /// <param name="name">Feedback変数の名前</param>
        /// <param name="varry">頂点データ</param>
        /// <param name="copyTo">コピー先のバッファー</param>
        /// <exception cref="NotImplementedException">このメソッドを呼び出す</exception>
        public void AddVertexArray (string name, VertexArray varry, VertexArray copyTo) {
            throw new NotImplementedException ("Sorry");
            /*
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            if (varry == null) {
                throw new ArgumentNullException ("VertexArray is null");
            }
            if (VertexCount > 0 && VertexCount != varry.VertexCount) {
                throw new ArgumentException ("All VertexCount must be equal in the same FeedbackBuffer");
            }
            // 同一のVertexArrayを異なる名前で登録するのもエラーにすべきだよね？

            this.feedbacks.RemoveAll (x => x.Name == name);
            this.feedbacks.Add (new FeedbackVariable (name, varry, copyTo));
             * */
        }

        /// <summary>
        /// 頂点アレイの取得
        /// </summary>
        /// <remarks>
        /// 登録済みのフィードバック変数の中から引数 <paramref name="name"/> のフィードバック変数のデータを収納するのに使用される頂点アレイを取得します。
        /// そのようなフィードバック変数が登録されていない場合、例外を発生します。
        /// </remarks>
        /// <param name="name">フィードバック変数名</param>
        /// <returns>頂点アレイ</returns>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> = <c>null</c> または空文字列</exception>
        /// <exception cref="InvalidOperationException"><paramref name=""/> が登録済みの名前ではない</exception>
        public VertexArray GetVertexArray (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            if (!feedbacks.Exists(x => x.Name == name)) {
                throw new InvalidOperationException ("Name is not registered");
            }
            return (from x in feedbacks
                    where x.Name == name
                    select x.VertexArray).First ();
        }

        /// <summary>
        /// フィードバック変数の取得
        /// </summary>
        /// <remarks>
        /// 登録済みのフィードバック変数の中から引数 <paramref name="name"/> のフィードバック変数を取得します。
        /// そのようなフィードバック変数が登録されていなかった場合、例外を発生します。
        /// </remarks>
        /// <param name="name">フィードバック変数名</param>
        /// <returns>フィードバック変数</returns>
        public FeedbackVariable GetFeedbackVarying (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            if (!feedbacks.Exists (x => x.Name == name)) {
                throw new InvalidOperationException ("Name is not registered");
            }
            return (from x in feedbacks
                    where x.Name == name
                    select x).First ();
        }



        /// <summary>
        /// フィードバック変数の削除
        /// </summary>
        /// <remarks>
        /// 登録済みのフィードバック変数の中から指定の名前のフィードバック変数を削除します。
        /// そのような名前のフィードバック変数が登録されていなかった場合単に無視します。
        /// </remarks>
        /// <param name="name">フィードバック変数名</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> が <c>null</c> または空文字列</exception>
        /// <returns>削除したら <c>true</c>、そうでなければ <c>false</c></returns>
        public bool RemoveVertexArray (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            if (!feedbacks.Exists (x => x.Name == name)) {
                return false;
            }
            this.feedbacks.RemoveAll (x => x.Name == name);
            return true;
        }

        /// <summary>
        //  フィードバック変数を取得するインデクサー
        /// </summary>
        /// <remarks>
        /// 指定の名前 <paramref name="name"/> のフィードバック変数を取得します。
        /// そのような名前のフィードバック変数が登録されていなかった場合、例外を発生します。
        /// </remarks>
        /// <param name="name">フィードバック変数名</param>
        /// <returns>フィードバック変数</returns>
        public FeedbackVariable this[string name] {
            get {
                if (!feedbacks.Exists (x => x.Name == name)) {
                    throw new InvalidOperationException ("Name is not registered");
                }
                return (from x in feedbacks
                        where x.Name == name
                        select x).First ();
            }
        }

        /// <inheritdoc/>
        public override string ToString () {
            return string.Format ("FeedbackBuffer: Feedbacks={0}", FeedbackVaryingCount);
        }
        #endregion


    }
}
